モザイクプロット
Contents
12. モザイクプロット¶
12.1. 概要¶
**モザイクプロット(Mosaic Plot)**とは,複数の質的変数に対して,その比率を四角形の面積で表したグラフです. その見た目からマリメッコプロット(Marimekko Plot)とも呼ばれます. 分割方法を工夫することで三変数以上にも対応可能ですが,私がよく見るのは二変数に対する描画です.
二変数に対するモザイクプロットは,積上げ棒グラフの棒の太さを,分母の大きさで調整したものと捉えることができます. これにより,二変数を跨いだ(他の棒中の要素との)比較が可能になりますが,目視で面積を測るのは難しい場合があるので,数値を付記すると親切です.

例えば上記は,雑誌別・年代別の作品数の比率を表したモザイクプロットです. 年代ごとの合計作品数に応じて,縦方向の棒の太さが変わっていることがわかります.
12.2. Plotlyによる作図方法¶
Plotlyで直接モザイクプロットを描画する方法はありません.こちらを参考に,棒グラフを応用して作図します. かなり複雑なので,詳細は以下の作図例にコメントを入れる形で解説します.
12.3. MADB Labを用いた作図例¶
12.3.1. 下準備¶
import pandas as pd
import plotly.express as px
import plotly.graph_objects as go
import warnings
warnings.filterwarnings('ignore')
# 前処理の結果,以下に分析対象ファイルが格納されていることを想定
PATH_DATA = '../../data/preprocess/out/episodes.csv'
# Jupyter Book用のPlotlyのrenderer
RENDERER = 'plotly_mimetype+notebook'
def add_years_to_df(df, unit_years=10):
"""unit_years単位で区切ったyears列を追加"""
df_new = df.copy()
df_new['years'] = \
pd.to_datetime(df['datePublished']).dt.year \
// unit_years * unit_years
df_new['years'] = df_new['years'].astype(str)
return df_new
def show_fig(fig):
"""Jupyter Bookでも表示可能なようRendererを指定"""
fig.update_layout(margin=dict(t=50, l=25, r=25, b=25))
fig.show(renderer=RENDERER)
df = pd.read_csv(PATH_DATA)
12.3.2. 雑誌別・年代別の合計作品数¶
col_count = 'cname'
# 10年単位で区切ったyearsを追加
df = add_years_to_df(df, 10)
# mcname, yearsで集計
df_plot = \
df.groupby(['mcname', 'years'])[col_count].\
nunique().reset_index()
# years単位で集計してdf_plotにカラムを追加
# モザイクプロットの太さを調整するために算出
df_tmp = df_plot.groupby('years')[col_count].sum().reset_index(
name='years_total')
df_plot = pd.merge(df_plot, df_tmp, how='left', on='years')
# years合計あたりの比率を計算
df_plot['ratio'] = df_plot[col_count] / df_plot['years_total']
# こんな感じでデータになった
df_plot
| mcname | years | cname | years_total | ratio | |
|---|---|---|---|---|---|
| 0 | 週刊少年サンデー | 1970 | 399 | 1593 | 0.250471 |
| 1 | 週刊少年サンデー | 1980 | 324 | 1488 | 0.217742 |
| 2 | 週刊少年サンデー | 1990 | 293 | 1289 | 0.227308 |
| 3 | 週刊少年サンデー | 2000 | 309 | 1490 | 0.207383 |
| 4 | 週刊少年サンデー | 2010 | 275 | 1471 | 0.186948 |
| 5 | 週刊少年ジャンプ | 1970 | 530 | 1593 | 0.332706 |
| 6 | 週刊少年ジャンプ | 1980 | 364 | 1488 | 0.244624 |
| 7 | 週刊少年ジャンプ | 1990 | 413 | 1289 | 0.320403 |
| 8 | 週刊少年ジャンプ | 2000 | 477 | 1490 | 0.320134 |
| 9 | 週刊少年ジャンプ | 2010 | 433 | 1471 | 0.294358 |
| 10 | 週刊少年チャンピオン | 1970 | 316 | 1593 | 0.198368 |
| 11 | 週刊少年チャンピオン | 1980 | 391 | 1488 | 0.262769 |
| 12 | 週刊少年チャンピオン | 1990 | 334 | 1289 | 0.259116 |
| 13 | 週刊少年チャンピオン | 2000 | 385 | 1490 | 0.258389 |
| 14 | 週刊少年チャンピオン | 2010 | 411 | 1471 | 0.279402 |
| 15 | 週刊少年マガジン | 1970 | 348 | 1593 | 0.218456 |
| 16 | 週刊少年マガジン | 1980 | 409 | 1488 | 0.274866 |
| 17 | 週刊少年マガジン | 1990 | 249 | 1289 | 0.193173 |
| 18 | 週刊少年マガジン | 2000 | 319 | 1490 | 0.214094 |
| 19 | 週刊少年マガジン | 2010 | 352 | 1471 | 0.239293 |
fig = go.Figure()
# mcnameごとにデータを抽出
for mcname in df_plot['mcname'].unique():
df_tmp = \
df_plot[df_plot['mcname']==mcname].reset_index(drop=True)
widths = df_tmp['years_total']
fig.add_trace(go.Bar(
name=mcname,
# x軸の基点を調整
x=df_tmp['years_total'].cumsum() - widths,
y=df_tmp['ratio'], text=df_tmp[col_count],
# 棒の太さを調整
width=widths,
offset=0,))
fig.update_xaxes(
# 目盛りの一を調整
tickvals=widths.cumsum() - widths/2,
ticktext=df_plot['years'].unique(),)
fig.update_xaxes(title='期間')
fig.update_yaxes(title='比率')
fig.update_layout(barmode='stack', title_text='雑誌別・年代別の合計作品数')
show_fig(fig)
12.3.3. 雑誌別・年代別の合計作家数¶
同じ要領で作家別に集計します.
col_count = 'creator'
# 10年単位で区切ったyearsを追加
df = add_years_to_df(df, 10)
# mcname, yearsで集計
df_plot = \
df.groupby(['mcname', 'years'])[col_count].\
nunique().reset_index()
# years単位で集計してdf_plotにカラムを追加
df_tmp = df_plot.groupby('years')[col_count].sum().reset_index(
name='years_total')
df_plot = pd.merge(df_plot, df_tmp, how='left', on='years')
# years合計あたりの比率を計算
df_plot['ratio'] = df_plot[col_count] / df_plot['years_total']
fig = go.Figure()
for mcname in df_plot['mcname'].unique():
df_tmp = \
df_plot[df_plot['mcname']==mcname].reset_index(drop=True)
widths = df_tmp['years_total']
fig.add_trace(go.Bar(
name=mcname,
x=df_tmp['years_total'].cumsum() - widths,
y=df_tmp['ratio'], text=df_tmp[col_count],
width=widths,
offset=0,))
fig.update_xaxes(
tickvals=widths.cumsum() - widths/2,
ticktext=df_plot['years'].unique(),)
fig.update_xaxes(title='期間')
fig.update_yaxes(title='比率')
fig.update_layout(
barmode='stack', title_text='雑誌別・年代別の合計作家数')
show_fig(fig)